from typing import Union, Optional
from enum import Enum
from fastapi import FastAPI, Depends, Query, Path, Request, Response
import uvicorn
import orjson
from fastapi.responses import Response, HTMLResponse

app = FastAPI(title="测试文档",
              description="这是一个简单的 demo",
              docs_url="/my_docs",
              openapi_url="/my_openapi",
              version='1.1.0')


class Name(str, Enum):
    satori = "古明地觉"
    koishi = "古明地恋"
    marisa = "雾雨魔理沙"


@app.get("/users/{user_name}")
async def get_user(user_name: Name):
    return {"user_id": user_name}


@app.get("/{flag}")
async def get_flag(flag: bool):
    return {"flag": flag}


@app.get("/user/{user_id}")
async def get_user(user_id: Union[int, str], name: Optional[str] = None):
    """通过 Union 来声明一个混合类型，int 在前、str 在后。会先按照 int 解析，解析失败再变成 str
       然后是 name，它表示字符串类型、但默认值为 None（不是字符串），那么应该声明为 Optional[str]"""
    return {"user_id": user_id, "name": name}


# 声明 file_path 的类型为 path，这样它会被当成一个整体
@app.get("/files/{file_path:path}")
async def get_file(file_path: str):
    return {"file_path": file_path}


@app.get("/apple/{item_id}")
async def get_item(item_id: int):
    """和 Flask 不同，Flask 定义类型是在路由当中，也就是在 <> 里面，变量和类型通过 : 分隔
       而 FastAPI 是使用类型注解的方式，此时的 item_id 要求一个整型（准确的说是一个能够转成整型的字符串）"""
    return {"item_id": item_id}


@app.get("/postgres/{schema}/v1/{table}")
async def get_data(schema: str,
                   table: str,
                   select: str = "*",
                   where: Optional[str] = None,
                   limit: Optional[int] = None,
                   offset: Optional[int] = None):
    """标准格式是：路径参数按照顺序在前，查询参数在后
       但其实对顺序是没有什么要求的"""
    query = f"select {select} from {schema}.{table}"
    if where:
        query += f" where {where}"
    if limit:
        query += f" limit {limit}"
    if offset:
        query += f" offset {offset}"
    return {"query": query}


async def common_parameters(q: Optional[str] = None, skip: int = 0, limit: int = 100):
    return {"q": q, "skip": skip, "limit": limit}


@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
    # common_parameters 接收三个参数：q、skip、limit
    # 然后在解析请求的时候，会将 q、skip、limit 传递到 common_parameters 中，然后将返回值赋值给 commons
    # 但如果解析不到某个参数时，那么会判断函数中参数是否有默认值，没有的话就会返回错误，而不是传递一个 None 进去
    return commons


@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
    return commons


@app.get("/user1/")
async def check_length(
        # 默认值为 None，应该声明为 Optional[str]，当然声明 str 也是可以的。只不过声明为 str，那么默认值应该也是 str
        # 所以如果一个类型允许为空，那么更规范的做法应该是声明为 Optional[类型]。
        password: Optional[str] = Query(None, min_length=6, max_length=15)
):
    return {"password": password}


@app.get("/user2/")
async def check_length(
        password: str = Query("satori", min_length=6, max_length=15, regex=r"^satori")
):
    """此时的 password 默认值为 'satori'，并且传递的时候必须要以 'satori' 开头
       但是值得注意的是 password 后面的是 str，不再是 Optional[str]，因为默认值不是 None 了
       当然这里即使写成 Optional[str] 也是没有什么影响的
    """
    return {"password": password}


@app.get("/user3/")
async def check_length(
        password: str = Query(..., min_length=6)
):
    """将第一个参数换成 ... 即可实现该参数是必传参数
    """
    return {"password": password}


@app.get("/items2/")
async def read_items(
        # 通过 url 的时候使用别名即可
        item1: Optional[str] = Query(None, alias="item-query"),
        item2: str = Query("哈哈", alias="@@@@"),
        item3: str = Query(..., alias="$$$$")  # item3 是必传的
):
    return {"item1": item1, "item2": item2, "item3": item3}


@app.get("/items3")
async def read_items(
        # item1 必须大于 5
        item1: int = Query(..., gt=5),
        # item2 必须小于等于 7
        item2: int = Query(..., le=7),
        # item3 必须必须等于 10
        item3: int = Query(..., ge=10, le=10)
):
    return {"item1": item1, "item2": item2, "item3": item3}


@app.get("/items/{item-id}")
async def read_items(item_id: int = Path(..., alias="item-id")):
    return {"item_id": item_id}


@app.get("/items1/{item-id}")
async def read_items(*, item_id: int = Path(..., alias="item-id"),
                     q: str):
    return {"item_id": item_id, "q": q}


@app.get("/items2/{item-id}")
async def read_items(q: str,
                     item_id: int = Path(..., alias="item-id")):
    return {"item_id": item_id, "q": q}


@app.get("/girl/{user_id}")
async def read_girl(user_id: str,
                    request: Request):
    """路径参数是必须要体现在参数中，但是查询参数可以不写了
       因为我们定义了 request: Request，那么请求相关的所有信息都会进入到这个 Request 对象中"""
    header = request.headers  # 请求头
    method = request.method  # 请求方法
    cookies = request.cookies  # cookies
    query_params = request.query_params  # 查询参数
    return {"name": query_params.get("name"), "age": query_params.get("age"), "hobby": query_params.getlist("hobby")}

@app.get("/girl1/{user_id}")
async def read_girl(user_id: str,
                    request: Request):
    query_params = request.query_params  # 查询参数
    data = {"name": query_params.get("name"), "age": query_params.get("age"), "hobby": query_params.getlist("hobby")}
    # 实例化一个 Response 对象
    response = Response(
        # content，我们需要手动转成 json 字符串，如果直接返回字典的话，那么在包装成 Response 对象的时候会自动帮你转
        orjson.dumps(data),
        # status_code，状态码
        201,
        # headers，响应头
        {"Token": "xxx"},
        # media_type，就是 HTML 中的 Content-Type
        "application/json",
    )
    # 如果想设置 cookie 的话，那么通过 response.set_cookie 即可
    # 删除 cookie 则是 response.delete_cookie
    return response

@app.get("/index/")
async def index():

    response1 = HTMLResponse("<h1>你好呀</h1>")
    response2 = Response("<h1>你好呀</h1>", media_type="text/html")
    # 以上两者是等价的，在 HTMLResponse 中会自动将 media_type 设置成 text/html
    return response1

if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5555, reload=True)
